// 环形队列
#pragma once
#include <vector> //数组模拟
#include <iostream>
#include "Sem.hpp" //需要有信号量
#include "Mutex.hpp"

static const int gcap = 5; // for debug

using namespace SemModule;
using namespace MutexModule;

template <typename T>
class RingQueue
{
public:
    RingQueue(int cap = gcap)
        : _cap(cap), // cap 是容量       默认是gcap
          _rq(cap),
          _blank_sem(cap),
          _p_step(0),
          _data_sem(0),
          _c_step(0)
    {
    }

    // 入队列
    void Equeue(const T &in)
    {
        // 生产者
        // 加锁方式1   在申请信号量之前加锁
        //  _pmutex.Lock();     //会竞争锁

        // 1.申请信号量，空位置信号量
        _blank_sem.P();
        {
            LockGuard lockguard(_pmutex); // 使用LockGuard 就不用手动申请释放了

            // 加锁方式2   在申请信号量之后加锁
            // _pmutex.Lock();

            // 2.生产
            _rq[_p_step] = in;

            // 3.更新下标
            ++_p_step;

            // 4.维持环形队列特性
            _p_step %= _cap;
            _data_sem.V();
        }

        // // 解锁
        // _pmutex.Unlock();
    }

    /*
        问题：对于加锁，是放在申请信号量之前？还是放在申请信号量之后？
        答案: 在申请前后都可以，但是放在之后加锁更好，效率更高，先把信号量资源给申请瓜分了，信号量申请不会超申请，
              之后再串行申请锁！！！
              如果先申请锁，竞争锁竞争成功的还要申请信号量，申请锁失败的线程还要先等到把锁释放了，竞争的锁
              才会去申请信号！
              先申请信号量，再加锁，再访问，访问期间其他线程也在申请信号量，准备资源，效率更高！
        所以，放之后更好！
    */

    // 出队列
    void Pop(T *out)
    {
        // 消费者
        // 1.申请信号量，数据信号量  （开始data信号量值为0，P操作会把他阻塞住，生产一个后，V操作一下才会改变，P操作成功）
        _data_sem.P();
        {
            // _cmutex.Lock(); // 先申请信号量，再加锁
            LockGuard lockguard(_pmutex);

            // 2.消费
            *out = _rq[_c_step];
            // 3.更新下标
            ++_c_step;
            // 4.维持环形特性
            _c_step %= _cap;
            _blank_sem.V();
        }
        // 解锁
        // _cmutex.Unlock();
    }

    ~RingQueue()
    {
    }

private:
    std::vector<T> _rq; // 定义出来环形队列
    int _cap;

    // 作为生产者 （关注的是格子资源）     需要信号量，所以要把信号量给带进来
    Sem _blank_sem; // 空位置
    // 无论生产和消费都要有对应的下标    生产和消费都是原子的，只有访问同一位置才会出现竞争，用信号量来保证互斥和同步
    int _p_step; // 生产者下标

    // 作为消费者  （关注的是数据层）
    Sem _data_sem; // 数据
    int _c_step;   // 消费者下标    用信号量来保证互斥和同步

    // 维护多生产，多消费，需要用锁，两把锁！！
    Mutex _cmutex; // 消费锁
    Mutex _pmutex; // 生产锁
};
